#include "game.h"
#include "gui.h"
#include "ai.h"
#include "units.h"
#include "network.h"



game::game( int _x ): x( _x )
{
	current_player = 0;
	ai_paused = true;
	replayed_game = false;
	network_game = false;
	score_history = 0;
	players_commands = 0;
	current_state = 0;
	testing_mode = false;
	game_ended = false;
	finished_buy = 0;
	buy_mode = false;
};



game::~game()
{
	if ( current_state != 0 )
		delete current_state;
	if ( score_history != 0 )
		delete score_history;
	if ( players_commands != 0 )
	{
		for ( vector< list< string >* >::iterator it = players_commands->begin(); it != players_commands->end(); ++it )
		{
			( *it )->clear();
			delete ( *it );
		}
		delete players_commands;
	}
};



game* game::get_instance()
{
	static game inst( 0 );
	return &inst;
};



void game::rebuild()
{
	if ( current_state != 0 )
		delete current_state;
	if ( score_history != 0 )
		delete score_history;
	if ( players_commands != 0 )
	{
		for ( vector< list< string >* >::iterator it = players_commands->begin(); it != players_commands->end(); ++it )
		{
			( *it )->clear();
			delete ( *it );
		}
		delete players_commands;
	}

	current_player = 0;
	ai_paused = true;
	network_game = false;
	score_history = 0;
	players_commands = 0;
	current_state = 0;
	testing_mode = false;
	game_ended = false;
	finished_buy = 0;
	buy_mode = false;
};



void game::get_next_ai_command()
{
	QTimer::singleShot( ( unsigned int)( ceil( parameters::get_instance()->ai_parameters[ "GENERAL" ][ "ai_delay" ] ) ), current_player->ai_algorithm, SLOT ( next_command() ) );
};



void game::get_next_ai_command_now()
{
	current_player->ai_algorithm->next_command();
};



void game::stop_ai()
{
	current_player->ai_algorithm->stop();
};



void game::start_buy( unsigned int start_player, bool replay )
{
	buy_mode = true;
	replayed_game = replay;
	//init players
	current_player = players::get_instance()->get_player( start_player );
	if ( !replay )
	{
		save_starting_objects();
		players_commands = new vector< list< string >* >( players::get_instance()->get_players_count() );
		for ( vector< list< string >* >::iterator it = players_commands->begin(); it != players_commands->end(); ++it )
			( *it ) = new list< string >;
	}
	//sets starting time and checks loaded units for each player
	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		players::get_instance()->get_player( i )->set_time( parameters::get_instance()->game_parameters[ "START_MONEY" ] );
	}

	check_loaded_units();
	//init game state
	current_state = new game_state( players::get_instance()->get_players_count(), start_player, players::get_instance()->pl, mapa::get_instance()->objects, mapa::get_instance()->terrain_clone() );
	//current_state->update_structures();
	up_to_date = true;
	game_gui::get_instance()->show_next_turn_buy();
	if ( current_player->get_type() == ai )
	{
		current_player->ai_algorithm->get_buy_commands( get_current_state() );
		get_next_ai_command_now();
	}
};



void game::start_game()
{
	buy_mode = false;
	//init players
	int current_player_id = current_player->get_id();
	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		++current_player_id;
		current_player_id = current_player_id % players::get_instance()->get_players_count();
		if ( players::get_instance()->get_player( current_player_id )->is_in_game() )
			break;
	}
	current_player = players::get_instance()->get_player( current_player_id );

	score_history = new vector< vector< double > >( players::get_instance()->get_players_count() );

	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		players::get_instance()->get_player( i )->set_time( parameters::get_instance()->game_parameters[ "START_TIME" ] );
	}

	//init game state
	current_state = new game_state( players::get_instance()->get_players_count(), current_player->get_id(), players::get_instance()->pl, mapa::get_instance()->objects, mapa::get_instance()->terrain_clone() );
	//current_state->update_structures();
	up_to_date = true;
	
	//init gui
	game_gui::get_instance()->start_game();
	game_gui::get_instance()->show_next_turn();

	if ( game::get_instance()->test_game_end() == 0 )
		set_score_to_history( current_state );

	if ( current_player->get_type() == ai )
	{
		current_player->ai_algorithm->get_commands( get_current_state() );
		if ( !ai_paused )
			get_next_ai_command();
		if ( is_in_testing_mode() )
			get_next_ai_command_now();
	}
};



void game::next_turn_buy()
{
	++finished_buy;
	if ( finished_buy == players::get_instance()->get_players_count() )
	{
		start_game();
		return;
	}

	int current_player_id = current_player->get_id();
	++current_player_id;
	current_player_id = current_player_id % players::get_instance()->get_players_count();

	current_player = players::get_instance()->get_player( current_player_id );

	up_to_date = false;
	current_state->clear_structures();
	game_gui::get_instance()->show_next_turn_buy();
	if ( current_player->get_type() == ai )
	{
		current_player->ai_algorithm->get_buy_commands( get_current_state() );
		get_next_ai_command_now();
	}
};



void game::next_turn()
{
	game::get_instance()->test_game_end();
	int current_player_id = current_player->get_id();
	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		++current_player_id;
		current_player_id = current_player_id % players::get_instance()->get_players_count();
		if ( players::get_instance()->get_player( current_player_id )->is_in_game() )
			break;
	}
	current_player = players::get_instance()->get_player( current_player_id );
	current_player->set_time( parameters::get_instance()->game_parameters[ "START_TIME" ] );
	up_to_date = false;	
	current_state->clear_structures();
	set_score_to_history( get_current_state() );
	game_gui::get_instance()->show_next_turn();
	if ( current_player->get_type() == ai )
	{
		current_player->ai_algorithm->get_commands( get_current_state() );
		if ( !ai_paused )
			get_next_ai_command();
		if ( is_in_testing_mode() )
			get_next_ai_command_now();
	}
};



void game::end_buy()
{
	next_turn_buy();
};



player* game::test_game_end()
{
	bool end = true;

	for ( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
	{
		player* pl = players::get_instance()->get_player( i );
		if ( ( pl->is_in_game() ) && ( pl->units.empty() ) )
			pl->set_out();
		if ( pl == current_player )
			continue;
		if ( pl->is_in_game() )
		{
			end = false;
		}
	}
	if ( end )
	{
		game_ended = true;
		cout << "GAME END - PLAYER " << current_player->get_id() << " WON" << endl;
		//set_score_to_history( get_current_state() );
		if ( current_player->get_type() == ai )
			current_player->ai_algorithm->stop();
		set_paused( true );
		if ( !replayed_game )
		{
			( *players_commands )[ current_player->get_id() ]->push_back( "NXT" );
		}
		game_gui::get_instance()->show_game_end( current_player );	
		return current_player;
	}
	return 0;
};



void game::command_received( player* pl, const string& command )
{
	if ( game_ended )
		return;
	bool failure = false;
	if ( pl != current_player )
		return;
	//no parameters
	//next turn in play mode
	if ( command == "NXT" )
	{
		if( !replayed_game )
			( *players_commands )[ pl->get_id() ]->push_back( command );
		if ( ( is_network_game() ) && ( pl->get_type() != network ) )
			network_client::get_instance()->deliver_command( pl, command );
		cout << "NEXT TURN" << endl;
		next_turn();
		return;
	}
	//end of buy mode
	else if ( command == "END" )
	{
		if( !replayed_game )
			( *players_commands )[ pl->get_id() ]->push_back( command );
		if ( ( is_network_game() ) && ( pl->get_type() != network ) )
			network_client::get_instance()->deliver_command( pl, command );
		cout << "END BUY" << endl;
		end_buy();
		return;
	}
	else
	{	
		//one parameter
		istringstream sstr( command );
		string c;
		unsigned int u;
		sstr >> c;
		sstr >> u;
		if ( sstr.fail() )
		{
			cout << "COMMAND FAILURE" << endl;
			failure = true;
		}
		else if ( c == "SEL" )
		{
			if( !replayed_game )
					( *players_commands )[ pl->get_id() ]->push_back( command );
			if ( current_player->sell_unit( u ) )
			{
				if ( ( is_network_game() ) && ( pl->get_type() != network ) )
					network_client::get_instance()->deliver_command( pl, command );
				cout << "UNIT " << u << " SOLD SUCCESSFULLY" << endl;
			}
			else
				cout << "CAN'T SELL UNIT " << u << endl;
		}
		else		
		{
			//three parameters
			unsigned int x;
			unsigned int y;
			sstr >> x;
			sstr >> y;
			if ( sstr.fail() )
			{
				cout << "COMMAND FAILURE" << endl;
				failure = true;
			}
			else if ( c == "ATT" )
			{
				if ( ( u < current_player->units.size() ) && ( current_player->units[ u ]->fire( x, y ) ) )
				{
					if( !replayed_game )
						( *players_commands )[ pl->get_id() ]->push_back( command );
					if ( ( is_network_game() ) && ( pl->get_type() != network ) )
						network_client::get_instance()->deliver_command( pl, command );
					cout << "ATTATCK TO [" << x << "," << y << "] WITH UNIT ID " << u << " SUCCESSFUL" << endl;
				}
				else
					cout << "CAN'T ATTATCK TO [" << x << "," << y << "] WITH UNIT ID " << u << endl;
			}
			else if ( c == "MOV" )
			{
				if ( ( u < current_player->units.size() ) && ( current_player->units[ u ]->move( x, y ) ) )
				{
					if( !replayed_game )
						( *players_commands )[ pl->get_id() ]->push_back( command );
					if ( ( is_network_game() ) && ( pl->get_type() != network ) )
						network_client::get_instance()->deliver_command( pl, command );
					cout << "MOVE TO [" << x << "," << y << "] WITH UNIT ID " << u << " SUCCESSFUL" << endl;
				}
				else
					cout << "CAN'T MOVE TO [" << x << "," << y << "] WITH UNIT ID " << u << endl;
			}
			else if ( c == "BUY" )
			{
				if( !replayed_game )
						( *players_commands )[ pl->get_id() ]->push_back( command );
				map< string, unit* >::iterator bit = prototypes::get_instance()->unit_pos_iterator( u );
				if ( ( bit != prototypes::get_instance()->units.end() ) && ( current_player->buy_unit( bit->second, x, y ) ) )
				{
					if ( ( is_network_game() ) && ( pl->get_type() != network ) )
						network_client::get_instance()->deliver_command( pl, command );
					cout << "UNIT " << prototypes::get_instance()->unit_pos_iterator( u )->second->get_name() << " BOUGHT AND PLACED TO [" << x << "," << y << "] SUCCESSFULLY" << endl;
				}
				else
					cout << "CAN'T BUY " << prototypes::get_instance()->unit_pos_iterator( u )->second->get_name() << " AND PLACE IT TO [" << x << "," << y << "]" << endl;
			}
			else
			{
				cout << "COMMAND FAILURE" << endl;
				failure = true;
			}
			
		}
	}
	if ( !failure )
	{
		if ( !buy_mode )
			game::get_instance()->test_game_end();
		up_to_date = false;
		current_state->clear_structures();
		if ( current_player->get_type() == ai )
		{
			if ( !ai_paused )
				get_next_ai_command();
			else if ( ( is_in_testing_mode() ) || ( buy_mode ) )
				get_next_ai_command_now();
		}
	}
};



player* game::get_current_player()
{
	return current_player;
};



void game::set_current_player( unsigned int n )
{
	current_player = players::get_instance()->get_player( n );
};



bool game::can_play( const player* p )
{
	return ( ( p == current_player ) && ( current_player->get_type() == local ) );
};



bool game::is_paused()
{
	return ai_paused;
};



void game::set_paused( bool paused )
{
	ai_paused = paused;
	game_gui::get_instance()->set_paused( paused );
	if ( !paused )
		if ( current_player->get_type() == ai )
			get_next_ai_command();
};



bool game::is_network_game()
{
	return network_game;
};



void game::set_network_game( bool net_game )
{
	network_game = net_game;
};



game_state* game::get_current_state()
{
	//if state is not up to date -> update it
	if ( !up_to_date )
	{
		delete current_state;
		current_state = new game_state( players::get_instance()->get_players_count(), current_player->get_id(), players::get_instance()->pl, mapa::get_instance()->objects, mapa::get_instance()->terrain_clone() );
		//current_state->update_structures();
		up_to_date = true;
	}
	return current_state;
};



void game::set_score_to_history( game_state* state )
{
	game_state* temp_state = state->clone();
	
	//HACK
	if ( temp_state == 0 )
		return;

	cout << "Round " << ( *score_history )[ temp_state->current_player_id ].size() << endl;
	//adds for each player his score to the history array
	for ( unsigned int i = 0; i < state->players_count; ++i )
	{
		temp_state->current_player_id = i;
		temp_state->update_score_structures();
		cout << "Player " << i << " score: " << temp_state->score << endl;
		( *score_history )[ temp_state->current_player_id ].push_back( temp_state->score );
	}

	delete temp_state;
};



vector< vector< double > >* game::get_score_history()
{
	return score_history;
};



void game::check_loaded_units()
{
	for ( unsigned int i = 0; i < mapa::get_instance()->get_size_x(); ++i )
		for ( unsigned int j = 0; j < mapa::get_instance()->get_size_y(); ++j )
		{	
			unit* u = ( dynamic_cast< unit* >( mapa::get_instance()->get_object( i, j ) ) );
			if ( ( u != 0 ) && ( u->get_nbr() < 0 ) )
			{
				//not enough time of units number limit reached
				if ( ( u->get_player()->get_time() < ( int )u->get_price() ) || ( u->get_player()->units.size() >= parameters::get_instance()->game_parameters[ "MAX_UNITS" ] ) )
				{
					//delete unit and clear structures
					mapa::get_instance()->set_object( 0, u->get_x(), u->get_y() );
					for ( vector< unit* >::iterator it = u->get_player()->units.begin(); it != u->get_player()->units.end(); ++it )
					{
						if ( *it == u )
						{
							u->get_player()->units.erase( it );
							break;
						}
					}
					delete u;
				}
				else
				{
					//lsubstract unit cost from time
					u->get_player()->set_time( u->get_player()->get_time() - u->get_price() );
				}
			}
		}
};



void game::save_starting_objects()
{
	ostringstream sout;
	sout << "#\tType\tName\tPos_x\tPos_y\tHealth\tPlayer_id" << endl << endl;
	for ( unsigned int p = 0; p < players::get_instance()->get_players_count(); ++p )
		for( vector< unit* >::iterator it = players::get_instance()->get_player( p )->units.begin(); it != players::get_instance()->get_player( p )->units.end(); ++it )
		{
			sout << "UNIT\t\"" << ( *it )->get_name() << "\"\t" << ( *it )->get_x() << "\t" << ( *it )->get_y() << "\t" << ( *it )->get_health() << "\t" << ( *it )->get_player()->get_id() << endl;
		}
	for ( unsigned int i = 0; i < mapa::get_instance()->get_instance()->get_size_x(); ++i )
		for( unsigned int j = 0; j < mapa::get_instance()->get_instance()->get_size_y(); ++j )
		{
			bonus* b = dynamic_cast< bonus* >( mapa::get_instance()->get_object( i, j ) );	
			if ( b != 0 )
			{
				sout << "BONUS\t\"" << b->get_name() << "\"\t" << b->get_x() << "\t" << b->get_y() << endl;
			}
		}

	starting_objects = sout.str();
};



void game::export_players_commands( const char* file )
{
	ofstream fout;
	fout.open( file, ios::trunc );
	if ( fout.good() )
	{
		fout << players::get_instance()->get_players_count();
		for( unsigned int i = 0; i < players::get_instance()->get_players_count(); ++i )
		{
			fout << endl << players::get_instance()->get_player( i )->get_name() << endl;
			for ( list< string >::iterator it = ( *players_commands )[ i ]->begin(); it != ( *players_commands )[ i ]->end(); ++it )
			{
				fout << ( *it ) << "#";
			}
		}
		fout.close();
		cout << "PLAYERS COMMANDS SAVED SUCCESSFULLY" << endl;
	}	
	else
	{
		cout << "ERROR WHILE SAVING PLAYERS COMMANDS" << endl;
		fout.close();
	}
};



void game::export_starting_objects( const char* file )
{
	ofstream fout;
	fout.open( file, ios::trunc );
	if ( fout.good() )
	{
		fout << starting_objects;
		fout.close();
		cout << "OBJECTS SAVED SUCCESSFULLY" << endl;
	}	
	else
	{
		cout << "ERROR WHILE SAVING OBJECTS" << endl;
		fout.close();
	}
};



string game::create_command( const string& c, unsigned int u, unsigned int x, unsigned int y )
{
	ostringstream sout;
	sout << c << " " << u << " " << x << " " << y;
	return sout.str();
};



string game::create_command( const string& c, unsigned int u )
{
	ostringstream sout;
	sout << c << " " << u;
	return sout.str();
};